package test.traffic;

import cn.legym.garp.gateway.GateWayApplication;
import cn.legym.garp.gateway.traffic.ParamSourceType;
import cn.legym.garp.gateway.traffic.dynamic.DynamicResourceManager;
import cn.legym.garp.gateway.traffic.dynamic.SentinelDatasourceWatcher;
import cn.legym.garp.gateway.traffic.dynamic.bean.DynamicParam;
import cn.legym.garp.gateway.traffic.dynamic.bean.DynamicResourceDefinition;
import cn.legym.garp.gateway.traffic.dynamic.bean.RuleMeta;
import cn.legym.garp.gateway.traffic.dynamic.watcher.InMemoryDatasourceWatcher;
import cn.legym.garp.gateway.traffic.rule.TrafficRule;
import com.alibaba.cloud.sentinel.datasource.RuleType;
import com.alibaba.csp.sentinel.property.SentinelProperty;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.fastjson.JSONObject;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.test.context.junit4.SpringRunner;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/**
 * create by pipisun on 2021/9/15
 * copyright © 2017-2021 Legym Technology Co.,Ltd. All rights reserve
 * d.
 * file description:
 * last update by {} on {}
 * update description:
 */
@EnableAutoConfiguration
@RunWith(SpringRunner.class)
@SpringBootTest(classes = GateWayApplication.class)
public class DynamicResourceTestcase {

    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    private DynamicResourceManager dynamicResourceManager;

    @Test
    public void testDefinitionSingleParam() {
        DynamicResourceDefinition definition = new DynamicResourceDefinition();
        definition.setName("test_dy_res");
        RuleMeta meta = new RuleMeta();
        meta.setRuleType(RuleType.FLOW);
        FlowRule rule = new FlowRule();
        rule.setResource("test_flow_rule");
        rule.setCount(42);
        meta.setContent(JSONObject.toJSONString(rule));
        definition.setMeta(meta);

        DynamicParam param = new DynamicParam();
        param.setFrom(ParamSourceType.URL);
        param.setKey("p1");

        List<DynamicParam> params = Collections.singletonList(param);
        definition.setParams(params);

        MockServerHttpRequest request = MockServerHttpRequest.get("https://some.web.site/abc?p1=foo").build();
        DynamicResourceDefinition.MatchResult result = definition.matchRequest(applicationContext, request);
        Assert.assertTrue(result.isSuccess());
        Assert.assertEquals(1, result.getResults().size());
        Assert.assertEquals(param.getKey(), result.getResults().get(0).getKey());
        Assert.assertEquals("foo", result.getResults().get(0).getValue());

        request = MockServerHttpRequest.get("https://some.web.site/abc?p2=foo").build();
        result = definition.matchRequest(applicationContext, request);
        Assert.assertFalse(result.isSuccess());

        // empty criteria, ok
        definition.setParams(Collections.emptyList());
        Assert.assertTrue(definition.matchRequest(applicationContext, request).isSuccess());
    }

    @Test
    public void testDefinitionCompositeParams() {
        DynamicResourceDefinition definition = new DynamicResourceDefinition();
        definition.setName("test_dy_res");
        RuleMeta meta = new RuleMeta();
        meta.setRuleType(RuleType.FLOW);
        FlowRule rule = new FlowRule();
        rule.setResource("test_flow_rule");
        rule.setCount(42);
        meta.setContent(JSONObject.toJSONString(rule));
        definition.setMeta(meta);

        DynamicParam param1 = new DynamicParam();
        param1.setFrom(ParamSourceType.RESTFUL_PATH);
        param1.setKey("/path/res/id/(\\w+)/?");
        DynamicParam param2 = new DynamicParam();
        param2.setFrom(ParamSourceType.HEADER);
        param2.setKey("Auth");

        List<DynamicParam> params = Arrays.asList(param1, param2);
        definition.setParams(params);

        // not all criteria met
        MockServerHttpRequest request = MockServerHttpRequest.get("https://some.web.site/path/res/id/foobar").build();
        DynamicResourceDefinition.MatchResult result = definition.matchRequest(applicationContext, request);
        Assert.assertFalse(result.isSuccess());// path ok, header miss

        //
        request = MockServerHttpRequest.get("https://some.web.site/path/res/id/foobar")
                .header("Auth", "headerValue").build();
        result = definition.matchRequest(applicationContext, request);
        Assert.assertTrue(result.isSuccess());
        Assert.assertEquals(params.size(), result.getResults().size());
        Assert.assertEquals(param1.getKey(), result.getResults().get(0).getKey());
        Assert.assertEquals("foobar", result.getResults().get(0).getValue());
        Assert.assertEquals(param2.getKey(), result.getResults().get(1).getKey());
        Assert.assertEquals("headerValue", result.getResults().get(1).getValue());

        // header ok, path miss
        request = MockServerHttpRequest.get("https://some.web.site/path/other/id/foobar")
                .header("Auth", "headerValue").build();
        result = definition.matchRequest(applicationContext, request);
        Assert.assertFalse(result.isSuccess());
    }

    @Test
    public void testResourceManagerDynamicResource() throws NoSuchFieldException, IllegalAccessException {
        // use in memory sentinel datasource watcher
        Field watcherField = dynamicResourceManager.getClass().getDeclaredField("sentinelDatasourceWatcher");
        watcherField.setAccessible(true);
        InMemoryDatasourceWatcher watcher = new InMemoryDatasourceWatcher();
        watcher.onInit(applicationContext, null);
        watcher.addListener(dynamicResourceManager);
        Object old = watcherField.get(dynamicResourceManager);
        if (old != null) {
            SentinelDatasourceWatcher oldWatcher = (SentinelDatasourceWatcher) old;
            oldWatcher.removeListener(dynamicResourceManager);
        }
        watcherField.set(dynamicResourceManager, watcher);

        // build definition
        DynamicResourceDefinition definition = new DynamicResourceDefinition();
        definition.setName("test_dy_res");
        RuleMeta meta = new RuleMeta();
        meta.setRuleType(RuleType.FLOW);
        FlowRule rule = new FlowRule();
        rule.setResource("test_flow_rule");
        rule.setCount(42);
        meta.setContent(JSONObject.toJSONString(rule));
        definition.setMeta(meta);

        DynamicParam param1 = new DynamicParam();
        param1.setFrom(ParamSourceType.RESTFUL_PATH);
        param1.setKey("/path/res/id/(\\w+)/?");
        DynamicParam param2 = new DynamicParam();
        param2.setFrom(ParamSourceType.HEADER);
        param2.setKey("Auth");

        List<DynamicParam> params = Arrays.asList(param1, param2);
        definition.setParams(params);

        // build rule
        TrafficRule trafficRule = new TrafficRule();
        trafficRule.setUrlPattern("whatever");// 已经过了规则匹配阶段
        trafficRule.setResource("test_dy_res");// 和自定义资源对应
        trafficRule.setDynamicResource(true);// true查找自定义资源 false直接触发Sentinel资源访问

        // fill definitions
        dynamicResourceManager.updateData(Collections.singletonList(definition));

        // trigger dynamic rule
        MockServerHttpRequest request = MockServerHttpRequest.get("https://some.web.site/path/res/id/foobar")
                .header("Auth", "head_value").build();
        if (trafficRule.getDynamicResource()) {
            boolean result = dynamicResourceManager.locateResource(request, trafficRule);
            Assert.assertTrue(result);
            List<FlowRule> dynamicRules = FlowRuleManager.getRules().stream().filter(r -> r.getResource().startsWith("$dynamic_")).collect(Collectors.toList());
            Assert.assertEquals(1, dynamicRules.size());
            Assert.assertEquals(String.format("$dynamic_%s$FLOW$#%s=%s#%s=%s", definition.getName(), param1.getKey(), "foobar", param2.getKey(), "head_value"), dynamicRules.get(0).getResource());

            // mock sentinel rules updating
            watcher.triggerDataUpdate(RuleType.FLOW, Collections.emptyList());
            dynamicRules = FlowRuleManager.getRules().stream().filter(r -> r.getResource().startsWith("$dynamic_")).collect(Collectors.toList());
            Assert.assertEquals(1, dynamicRules.size());
            Assert.assertEquals(String.format("$dynamic_%s$FLOW$#%s=%s#%s=%s", definition.getName(), param1.getKey(), "foobar", param2.getKey(), "head_value"), dynamicRules.get(0).getResource());

            // definition update
            dynamicResourceManager.updateData(Collections.emptyList());
            dynamicRules = FlowRuleManager.getRules().stream().filter(r -> r.getResource().startsWith("$dynamic_")).collect(Collectors.toList());
            Assert.assertTrue(dynamicRules.isEmpty());
        }

//        dynamicResourceManager.updateData(null);
//        dynamicResourceManager.locateResource()

    }
}
